// Copyright 2016 The Draco Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef DRACO_COMPRESSION_MESH_MESH_EDGEBREAKER_TRAVERSAL_VALENCE_DECODER_H_
#define DRACO_COMPRESSION_MESH_MESH_EDGEBREAKER_TRAVERSAL_VALENCE_DECODER_H_

#include "draco/draco_features.h"

#include "draco/compression/mesh/mesh_edgebreaker_traversal_decoder.h"
#include "draco/core/symbol_decoding.h"
#include "draco/core/varint_decoding.h"

namespace draco {

// Decoder for traversal encoded with MeshEdgeBreakerTraversalValenceEncoder.
// The decoder maintains valences of the decoded portion of the traversed mesh
// and it uses them to select entropy context used for decoding of the actual
// symbols.
class MeshEdgeBreakerTraversalValenceDecoder
    : public MeshEdgeBreakerTraversalDecoder {
  public:
    MeshEdgeBreakerTraversalValenceDecoder()
        : corner_table_(nullptr),
          num_vertices_(0),
          last_symbol_(-1),
          active_context_(-1),
          min_valence_(2),
          max_valence_(7) {}
    void Init(MeshEdgeBreakerDecoderImplInterface *decoder) {
        MeshEdgeBreakerTraversalDecoder::Init(decoder);
        corner_table_ = decoder->GetCornerTable();
    }
    void SetNumEncodedVertices(int num_vertices) {
        num_vertices_ = num_vertices;
    }

    bool Start(DecoderBuffer *out_buffer) {
#ifdef DRACO_BACKWARDS_COMPATIBILITY_SUPPORTED
        if (BitstreamVersion() < DRACO_BITSTREAM_VERSION(2, 2)) {
            if (!MeshEdgeBreakerTraversalDecoder::DecodeTraversalSymbols())
                return false;
        }
#endif
        if (!MeshEdgeBreakerTraversalDecoder::DecodeStartFaces())
            return false;
        if (!MeshEdgeBreakerTraversalDecoder::DecodeAttributeSeams())
            return false;
        *out_buffer = *buffer();

#ifdef DRACO_BACKWARDS_COMPATIBILITY_SUPPORTED
        if (BitstreamVersion() < DRACO_BITSTREAM_VERSION(2, 2)) {
            uint32_t num_split_symbols;
            if (BitstreamVersion() < DRACO_BITSTREAM_VERSION(2, 0)) {
                if (!out_buffer->Decode(&num_split_symbols))
                    return false;
            } else {
                if (!DecodeVarint(&num_split_symbols, out_buffer))
                    return false;
            }
            if (num_split_symbols >= num_vertices_)
                return false;

            int8_t mode;
            if (!out_buffer->Decode(&mode))
                return false;
            if (mode == EDGEBREAKER_VALENCE_MODE_2_7) {
                min_valence_ = 2;
                max_valence_ = 7;
            } else {
                // Unsupported mode.
                return false;
            }
        } else
#endif
        {
            min_valence_ = 2;
            max_valence_ = 7;
        }

        if (num_vertices_ < 0)
            return false;
        // Set the valences of all initial vertices to 0.
        vertex_valences_.resize(num_vertices_, 0);

        const int num_unique_valences = max_valence_ - min_valence_ + 1;

        // Decode all symbols for all contexts.
        context_symbols_.resize(num_unique_valences);
        context_counters_.resize(context_symbols_.size());
        for (int i = 0; i < context_symbols_.size(); ++i) {
            uint32_t num_symbols;
            DecodeVarint<uint32_t>(&num_symbols, out_buffer);
            if (num_symbols > 0) {
                context_symbols_[i].resize(num_symbols);
                DecodeSymbols(num_symbols, 1, out_buffer, context_symbols_[i].data());
                // All symbols are going to be processed from the back.
                context_counters_[i] = num_symbols;
            }
        }
        return true;
    }

    inline uint32_t DecodeSymbol() {
        // First check if we have a valid context.
        if (active_context_ != -1) {
            const int context_counter = --context_counters_[active_context_];
            if (context_counter < 0)
                return TOPOLOGY_INVALID;
            const int symbol_id = context_symbols_[active_context_][context_counter];
            last_symbol_ = edge_breaker_symbol_to_topology_id[symbol_id];
        } else {
#ifdef DRACO_BACKWARDS_COMPATIBILITY_SUPPORTED
            if (BitstreamVersion() < DRACO_BITSTREAM_VERSION(2, 2)) {
                // We don't have a predicted symbol or the symbol was mis-predicted.
                // Decode it directly.
                last_symbol_ = MeshEdgeBreakerTraversalDecoder::DecodeSymbol();
            } else
#endif
            {
                // The first symbol must be E.
                last_symbol_ = TOPOLOGY_E;
            }
        }
        return last_symbol_;
    }

    inline void NewActiveCornerReached(CornerIndex corner) {
        const CornerIndex next = corner_table_->Next(corner);
        const CornerIndex prev = corner_table_->Previous(corner);
        // Update valences.
        switch (last_symbol_) {
        case TOPOLOGY_C:
        case TOPOLOGY_S:
            vertex_valences_[corner_table_->Vertex(next)] += 1;
            vertex_valences_[corner_table_->Vertex(prev)] += 1;
            break;
        case TOPOLOGY_R:
            vertex_valences_[corner_table_->Vertex(corner)] += 1;
            vertex_valences_[corner_table_->Vertex(next)] += 1;
            vertex_valences_[corner_table_->Vertex(prev)] += 2;
            break;
        case TOPOLOGY_L:
            vertex_valences_[corner_table_->Vertex(corner)] += 1;
            vertex_valences_[corner_table_->Vertex(next)] += 2;
            vertex_valences_[corner_table_->Vertex(prev)] += 1;
            break;
        case TOPOLOGY_E:
            vertex_valences_[corner_table_->Vertex(corner)] += 2;
            vertex_valences_[corner_table_->Vertex(next)] += 2;
            vertex_valences_[corner_table_->Vertex(prev)] += 2;
            break;
        default:
            break;
        }
        // Compute the new context that is going to be used to decode the next
        // symbol.
        const int active_valence = vertex_valences_[corner_table_->Vertex(next)];
        int clamped_valence;
        if (active_valence < min_valence_) {
            clamped_valence = min_valence_;
        } else if (active_valence > max_valence_) {
            clamped_valence = max_valence_;
        } else {
            clamped_valence = active_valence;
        }

        active_context_ = (clamped_valence - min_valence_);
    }

    inline void MergeVertices(VertexIndex dest, VertexIndex source) {
        // Update valences on the merged vertices.
        vertex_valences_[dest] += vertex_valences_[source];
    }

  private:
    const CornerTable *corner_table_;
    int num_vertices_;
    IndexTypeVector<VertexIndex, int> vertex_valences_;
    int last_symbol_;
    int active_context_;

    int min_valence_;
    int max_valence_;
    std::vector<std::vector<uint32_t>> context_symbols_;
    // Points to the active symbol in each context.
    std::vector<int> context_counters_;
};

}  // namespace draco

#endif  // DRACO_COMPRESSION_MESH_MESH_EDGEBREAKER_TRAVERSAL_VALENCE_DECODER_H_
